﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;

namespace SHomeWorkshop.LunarMind
{
    /// <summary>
    /// 为XmlNode添加了几个扩展方法。
    /// </summary>
    public static class XmlTools
    {

        /// <summary>
        /// [扩展方法]将一串Xml格式的文本转换成XmlNode节点，并插入为此节点的子节点。
        /// 
        /// 异常：
        /// 　　ArgumentNullException
        /// </summary>
        /// <param name="xmlNode">调用本方法的Xml节点（本节点将成为新节点的父节点）。</param>
        /// <param name="xmlText">需要转换成新子节点的Xml文本（需要符合Xml格式）。</param>
        /// <returns>如果成功转换并插入到当前节点尾部，成为当前节点的最后一个子节点，就返回【新子节点】。失败则返回null。</returns>
        public static XmlNode AppendXmlAsChild(this XmlNode xmlNode, String xmlText)
        {

            if (xmlText == null || xmlText.Length <= 0)
                throw new ArgumentNullException("XmlNode的AppendXmlAsChild()扩展方法的“xmlText”参数值不能为null或空字符串！");

            XmlDocument doc = new XmlDocument();
            doc.LoadXml(xmlText);
            XmlNode newNode = xmlNode.OwnerDocument.CreateNode
                (doc.FirstChild.NodeType, doc.FirstChild.Name, doc.FirstChild.NamespaceURI);

            foreach (XmlAttribute attribute in doc.FirstChild.Attributes)
            {
                XmlAttribute newAttribute = xmlNode.OwnerDocument.CreateAttribute(attribute.Name);
                newAttribute.Value = attribute.Value;
                newNode.Attributes.Append(newAttribute);
            }

            newNode.InnerXml = doc.FirstChild.InnerXml;

            xmlNode.AppendChild(newNode);

            return newNode;
        }

        public static XmlElement AppendXmlAsChild(this XmlElement xmlElement, String xmlText)
        {

            if (xmlText == null || xmlText.Length <= 0)
                throw new ArgumentNullException("XmlNode的AppendXmlAsChild()扩展方法的“xmlText”参数值不能为null或空字符串！");

            XmlDocument doc = new XmlDocument();
            doc.LoadXml(xmlText);
            XmlElement newElement = xmlElement.OwnerDocument.CreateElement(doc.FirstChild.Name, doc.FirstChild.NamespaceURI);

            foreach (XmlAttribute attribute in doc.FirstChild.Attributes)
            {
                XmlAttribute newAttribute = xmlElement.OwnerDocument.CreateAttribute(attribute.Name);
                newAttribute.Value = attribute.Value;
                newElement.Attributes.Append(newAttribute);
            }

            newElement.InnerXml = doc.FirstChild.InnerXml;

            xmlElement.AppendChild(newElement);

            return newElement;
        }

        /// <summary>
        /// 取出当前节点中指定名称的属性。
        /// </summary>
        /// <param name="xmlNode">要取属性的节点。</param>
        /// <param name="attributeName">要取的属性的名称。</param>
        /// <returns>返回指定名称的属性。如果不存在，返回null。</returns>
        public static XmlAttribute GetAttribute(this XmlNode xmlNode, String attributeName)
        {
            if (xmlNode == null) return null;
            if (xmlNode.Attributes == null) return null;

            XmlAttributeCollection attributes = xmlNode.Attributes;
            foreach (XmlAttribute attribute in attributes)
            {
                if (attribute.Name == attributeName)
                {
                    return attribute;
                }

                if (("ws_" + attributeName) == attribute.Name){
                    return attribute;
                }
            }

            return null;
        }

        /// <summary>
        /// 移除指定名称的属性。
        /// </summary>
        /// <param name="xmlNode">节点。</param>
        /// <param name="attributeName">属性名。</param>
        /// <returns>是否移除成功。</returns>
        public static bool RemoveAttributeByName(this XmlNode xmlNode, string attributeName)
        {
            if (xmlNode == null) return false;
            if (xmlNode.Attributes == null) return false;

            XmlAttributeCollection attributes = xmlNode.Attributes;
            foreach (XmlAttribute attribute in attributes)
            {
                if (attribute.Name == attributeName)
                {
                    xmlNode.Attributes.Remove(attribute);
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// 删除此节点的所有子节点（node）。但不删除Attributes。
        /// </summary>
        /// <param name="xmlElement"></param>
        public static void RemoveAllChildNodes(this XmlNode xmlNode)
        {
            if (xmlNode == null) return;
            if (xmlNode.ChildNodes.Count <= 0) return;

            for (int i = xmlNode.ChildNodes.Count - 1; i >= 0; i--)
            {
                xmlNode.RemoveChild(xmlNode.ChildNodes[i]);
            }
        }

        /// <summary>
        /// 取XmlElement的属性。
        /// </summary>
        /// <param name="xmlElement">源——这是一个附加方法，
        /// 应由此元素调用本方法（虽然是静态方法，但应按实例方法的式样调用）。</param>
        /// <param name="attributeName">属性名。</param>
        /// <returns>返回属性。</returns>
        public static XmlAttribute GetAttributeByName(this XmlElement xmlElement, String attributeName)
        {
            if (xmlElement == null) return null;
            if (xmlElement.Attributes == null) return null;

            XmlAttributeCollection attributes = xmlElement.Attributes;
            foreach (XmlAttribute attribute in attributes)
            {
                if (attribute.Name == attributeName)
                {
                    return attribute;
                }
            }

            return null;
        }

        /// <summary>
        /// [扩展方法]取出节点中指定名称的属性的文本值。如果不存在，返回null，而不是返回空字符串。
        /// </summary>
        /// <param name="xmlNode">调用本方法的XmlNode节点（取其属性值的节点）。</param>
        /// <param name="attributeName">要取值的属性的名称。</param>
        /// <returns>返回属性的值（文本值）。如果不存在该属性，返回null。</returns>
        public static String GetAttributeValueText(this XmlNode xmlNode, String attributeName, string defaultValue = "")
        {
            XmlAttributeCollection attributes = xmlNode.Attributes;
            foreach (XmlAttribute attribute in attributes)
            {
                if (attribute.Name == attributeName)
                {
                    return attribute.InnerText;
                }
            }

            return (defaultValue == null ? null : defaultValue);
        }

        /// <summary>
        /// 取出指定节点的指定名称属性的值。
        /// </summary>
        /// <param name="xmlNode">要取其属性值的节点。</param>
        /// <param name="attributeName">要取值的属性的名称。</param>
        /// <returns>返回属性的值（文本值）。如果不存在该属性，返回null。</returns>
        public static String GetAttributeString(this XmlNode xmlNode, String attributeName)
        {
            XmlAttributeCollection attributes = xmlNode.Attributes;
            foreach (XmlAttribute attribute in attributes)
            {
                if (attribute.Name == attributeName)
                {
                    return attribute.InnerText;
                }
            }

            return null;
        }

        /// <summary>
        /// 将提供的Xml文本转换成一个XmlNode并插入到自身的后面或前面。
        /// 新节点与当前调用本方法的节点平级。
        /// 注意：只能传入一个节点，不能同时传入多个节点。
        /// </summary>
        /// <param name="xmlNode">新节点将与本节点平级。</param>
        /// <param name="xmlText">新节点的Xml文本。</param>
        /// <param name="afterMe">默认为：true，表示在本节点之后；false表示在本节点之前插入。</param>
        /// <returns>如果插入成功，返回true。</returns>
        public static bool InsertXml(this XmlNode xmlNode, String xmlText, bool afterMe = true)
        {
            XmlNode parentNode = xmlNode.ParentNode;
            XmlDocument doc = new XmlDocument();
            try
            {
                doc.LoadXml(xmlText);

                XmlNode newNode = xmlNode.OwnerDocument.CreateNode
                    (doc.FirstChild.NodeType, doc.FirstChild.Name, doc.FirstChild.NamespaceURI);

                foreach (XmlAttribute attribute in doc.FirstChild.Attributes)
                {
                    XmlAttribute newAttribute = xmlNode.OwnerDocument.CreateAttribute(attribute.Name);
                    newAttribute.Value = attribute.Value;
                    newNode.Attributes.Append(newAttribute);
                }

                newNode.InnerXml = doc.FirstChild.InnerXml;

                if (afterMe)
                {
                    parentNode.InsertAfter(newNode, xmlNode);
                }
                else
                {
                    parentNode.InsertBefore(newNode, xmlNode);
                }

                return true;
            }
            catch (XmlException e)
            {
                Dialogs.LunarMsgBox.MsgBoxShow(
                    Globals.AppName,
                    "传入的Xml文本不符合要求。",
                    "异常消息如下：\r\n" + e.Message,
                    e.StackTrace,
                    System.Windows.MessageBoxButton.OK,
                    System.Windows.MessageBoxImage.Warning);
                return false;
            }

        }

        /// <summary>
        /// 在指定索引处插入新的Xml节点。
        /// </summary>
        /// <param name="xmlNode">父节点。</param>
        /// <param name="xmlText">新子节点的OutXml文本。</param>
        /// <param name="index">新子节点应出现在父节点中哪个位置。</param>
        /// <returns></returns>
        public static XmlNode InsertXmlAt(this XmlNode xmlNode, String xmlText, int index)
        {
            if (index < 0 || xmlText == null || xmlText.Length <= 0 ||
                xmlNode.ChildNodes == null || index > xmlNode.ChildNodes.Count)
                return null;

            XmlDocument doc = new XmlDocument();
            try
            {
                doc.LoadXml(xmlText);

                XmlNode newNode = xmlNode.OwnerDocument.CreateNode
                    (doc.FirstChild.NodeType, doc.FirstChild.Name, doc.FirstChild.NamespaceURI);

                foreach (XmlAttribute attribute in doc.FirstChild.Attributes)
                {
                    XmlAttribute newAttribute = xmlNode.OwnerDocument.CreateAttribute(attribute.Name);
                    newAttribute.Value = attribute.Value;
                    newNode.Attributes.Append(newAttribute);
                }

                newNode.InnerXml = doc.FirstChild.InnerXml;

                if (index > 0)
                {
                    XmlNode preNode = xmlNode.ChildNodes[index - 1];
                    if (preNode == null) return null;

                    return xmlNode.InsertAfter(newNode, preNode);
                }
                else if (index == 0)
                {
                    return xmlNode.InsertBefore(newNode, xmlNode.FirstChild);
                }
                else return null;
            }
            catch (XmlException e)
            {
                Dialogs.LunarMsgBox.MsgBoxShow(
                    Globals.AppName,
                    "传入的Xml文本不符合要求。",
                    "异常消息如下：\r\n" + e.Message,
                    e.StackTrace,
                    System.Windows.MessageBoxButton.OK,
                    System.Windows.MessageBoxImage.Warning);
                return null;
            }
        }

        public static bool InsertAt(this XmlNode xmlNode, int index, XmlNode newXmlChildNode)
        {
            if (index < 0) return false;

            if (index == 0)
            {
                if (xmlNode.FirstChild != null)
                {
                    xmlNode.InsertBefore(newXmlChildNode, xmlNode.FirstChild);
                    return true;
                }
                else
                {
                    xmlNode.AppendChild(newXmlChildNode);
                    return true;
                }
            }

            XmlNodeList childNodeList = xmlNode.ChildNodes;
            if (index > childNodeList.Count) return false;

            xmlNode.InsertAfter(newXmlChildNode, childNodeList[index - 1]);
            return true;
        }

        /// <summary>
        /// 将Xml格式的文本以节点的身份插入为此节点的子节点。
        /// </summary>
        /// <param name="xmlNode"></param>
        /// <param name="xmlText">需要符合Xml格式。</param>
        /// <returns></returns>
        public static XmlNode AppendXml(this XmlNode xmlNode, String xmlText)
        {
            if (xmlText == null || xmlText.Length <= 0 ||
                xmlNode.ChildNodes == null)
                return null;

            XmlDocument doc = new XmlDocument();
            try
            {
                doc.LoadXml(xmlText);

                XmlNode newNode = xmlNode.OwnerDocument.CreateNode
                    (doc.FirstChild.NodeType, doc.FirstChild.Name, doc.FirstChild.NamespaceURI);

                foreach (XmlAttribute attribute in doc.FirstChild.Attributes)
                {
                    XmlAttribute newAttribute = xmlNode.OwnerDocument.CreateAttribute(attribute.Name);
                    newAttribute.Value = attribute.Value;
                    newNode.Attributes.Append(newAttribute);
                }

                newNode.InnerXml = doc.FirstChild.InnerXml;

                xmlNode.AppendChild(newNode);

                return newNode;
            }
            catch (XmlException e)
            {
                Dialogs.LunarMsgBox.MsgBoxShow(
                    Globals.AppName,
                    "传入的Xml文本不符合要求。",
                    "异常消息如下：\n" + e.Message,
                    e.StackTrace,
                    System.Windows.MessageBoxButton.OK,
                    System.Windows.MessageBoxImage.Warning);
                return null;
            }
        }

        /// <summary>
        /// 为节点设置一个属性值。如果已存在该属性，则更新其值；若尚无该属性，则添加一个新属性。
        /// </summary>
        /// <param name="xmlNode">要设置属性的节点。</param>
        /// <param name="attributeName">属性名。</param>
        /// <param name="attributeValue">属性值。</param>
        /// <returns></returns>
        public static bool SetAttribute(this XmlNode xmlNode, String attributeName, String attributeValue)
        {
            XmlAttributeCollection attributes = xmlNode.Attributes;
            XmlAttribute attribute = null;

            foreach (XmlAttribute a in attributes)
            {
                if (a.Name == attributeName)
                {
                    attribute = a;
                    break;
                }
            }

            if (attribute == null)
            {
                XmlAttribute newAttribute = xmlNode.OwnerDocument.CreateAttribute(attributeName);
                newAttribute.InnerText = attributeValue;
                xmlNode.Attributes.Append(newAttribute);
                return true;
            }
            else
            {
                if (attribute.InnerText != attributeValue)
                {
                    attribute.InnerText = attributeValue;
                }
                return true;
            }
        }

        /// <summary>
        /// 将调用本方法的节点移动到前一个位置。
        /// </summary>
        /// <param name="xmlNode"></param>
        /// <returns></returns>
        public static bool MoveToPrevious(this XmlNode xmlNode)
        {
            XmlNode previousNode = xmlNode.PreviousSibling;
            if (previousNode == null)
                return false;

            XmlNode parentNode = xmlNode.ParentNode;
            if (parentNode == null) return false;

            parentNode.RemoveChild(xmlNode);
            parentNode.InsertBefore(xmlNode, previousNode);
            return true;
        }

        public static bool MoveToNext(this XmlNode xmlNode)
        {
            XmlNode nextNode = xmlNode.NextSibling;
            if (nextNode == null) return false;

            XmlNode parentNode = xmlNode.ParentNode;
            if (parentNode == null) return false;

            parentNode.RemoveChild(xmlNode);
            parentNode.InsertAfter(xmlNode, nextNode);
            return true;
        }

        public static int IndexOf(this XmlNode xmlNode, XmlNode childNode)
        {
            if (childNode == null) return -1;

            XmlNode parentNode = xmlNode;
            if (parentNode == null) return -1;

            int curIndex = -1;
            for (int i = 0; i < parentNode.ChildNodes.Count; i++)
            {
                XmlNode node = parentNode.ChildNodes[i];
                if (node == childNode)
                {
                    curIndex = i; break;
                }
            }

            return curIndex;
        }

        /// <summary>
        /// 将当前节点移动到指定位置。
        /// </summary>
        /// <param name="xmlNode">当前节点。本方法由此节点以实例方式调用。</param>
        /// <param name="newIndex">当前节点的新索引。</param>
        /// <returns>如果成功返回真。</returns>
        public static bool MoveTo(this XmlNode xmlNode, int newIndex)
        {
            if (newIndex < 0) return false;

            XmlNode parentNode = xmlNode.ParentNode;
            if (parentNode == null) return false;

            if (newIndex >= parentNode.ChildNodes.Count) return false;

            int curIndex = parentNode.IndexOf(xmlNode);

            if (newIndex < curIndex)
            {
                parentNode.RemoveChild(xmlNode);
                parentNode.InsertBefore(xmlNode, parentNode.ChildNodes[newIndex]);
                return true;
            }
            else if (newIndex > curIndex)
            {
                XmlNode baseDestNode = parentNode.ChildNodes[newIndex];
                if (baseDestNode == null) return false;

                parentNode.RemoveChild(xmlNode);
                parentNode.InsertAfter(xmlNode, baseDestNode);
                return true;
            }
            else return false;
        }

        /// <summary>
        /// 替换当前节点的OutXmlText。
        /// </summary>
        /// <param name="node">要替换的节点。</param>
        /// <param name="newOutXmlText">节点的新OutXml文本。</param>
        /// <returns>返回被替换后的新节点。</returns>
        public static XmlNode ReplaceOutXmlText(XmlNode xmlNode, string xmlText)
        {
            XmlNode parentNode = xmlNode.ParentNode;
            XmlDocument doc = new XmlDocument();
            try
            {
                doc.LoadXml(xmlText);

                XmlNode newNode = xmlNode.OwnerDocument.CreateNode
                    (doc.FirstChild.NodeType, doc.FirstChild.Name, doc.FirstChild.NamespaceURI);

                foreach (XmlAttribute attribute in doc.FirstChild.Attributes)
                {
                    XmlAttribute newAttribute = xmlNode.OwnerDocument.CreateAttribute(attribute.Name);
                    newAttribute.Value = attribute.Value;
                    newNode.Attributes.Append(newAttribute);
                }

                newNode.InnerXml = doc.FirstChild.InnerXml;

                if (parentNode.ReplaceChild(newNode, xmlNode) != null)
                {
                    return newNode;
                }
                else return null;
            }
            catch (XmlException e)
            {
                Dialogs.LunarMsgBox.MsgBoxShow(
                    Globals.AppName,
                    "传入的Xml文本不符合要求。",
                    "异常消息如下：\n" + e.Message,
                    e.StackTrace,
                    System.Windows.MessageBoxButton.OK,
                    System.Windows.MessageBoxImage.Warning);
                return null;
            }
        }

        /// <summary>
        /// 将Xml序列字符串保存为一个磁盘上的Xml文件。
        /// </summary>
        /// <param name="fullPathOfXmlFile">文件路径。</param>
        /// <param name="xmlString">Xml字符串。</param>
        /// <returns></returns>
        public static XmlDocument SaveToXmlFile(string fullPathOfXmlFile, string xmlString)
        {
            if (fullPathOfXmlFile == null || fullPathOfXmlFile.Length <= 0) return null;
            if (xmlString == null || xmlString.Length <= 0) return null;

            if (xmlString.StartsWith(XmlTools.XmlHeaderText) == false)
            {
                xmlString = XmlTools.XmlHeaderText + xmlString;
            }

            try
            {
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(xmlString);

                string directoryString;

                int indexOfLastSeparator = fullPathOfXmlFile.LastIndexOf("\\");
                if (indexOfLastSeparator == -1)
                {
                    return null;
                }
                else
                {
                    directoryString = fullPathOfXmlFile.Substring(0, indexOfLastSeparator);
                    if (Directory.Exists(directoryString) == false)
                    {
                        Directory.CreateDirectory(directoryString);
                        if (Directory.Exists(directoryString) == false) return null;//未能创建目录。
                    }
                }

                //using (System.Xml.XmlTextWriter writer = new System.Xml.XmlTextWriter(fullPathOfXmlFile, Encoding.UTF8))
                //{
                //    writer.Formatting = Formatting.Indented;
                //    doc.WriteTo(writer);
                //}

                doc.Save(fullPathOfXmlFile);

                return doc;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// 从磁盘上读取Xml文件，如果正常，返回一个XmlDocument对象。
        /// </summary>
        /// <param name="fileFullPath">Xml文件在磁盘上的完整路径。</param>
        /// <returns></returns>
        public static XmlDocument LoadXmlFile(string fileFullPath)
        {
            XmlReader reader = null;

            try
            {
                XmlReaderSettings settings = new XmlReaderSettings();
                settings.ConformanceLevel = ConformanceLevel.Document;
                settings.IgnoreWhitespace = true;
                settings.IgnoreComments = true;

                string uriStr = fileFullPath;// fileFullPath.StartsWith("file:///") ? fileFullPath : "file:///" + fileFullPath;

                reader = XmlReader.Create(uriStr, settings);

                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.RemoveAll();

                System.Xml.XmlNode node = xmlDoc.ReadNode(reader); ;

                while (node != null)
                {
                    xmlDoc.AppendChild(node);
                    node = xmlDoc.ReadNode(reader);
                }

                return xmlDoc;
            }
            catch (Exception ex)
            {
                Dialogs.LunarMsgBox.MsgBoxShow(
                    Globals.AppName,
                    "读取Xml文件时发生意外。操作失败。",
                    "异常信息如下：\r\n" + ex.Message,
                    ex.StackTrace,
                    System.Windows.MessageBoxButton.OK,
                    System.Windows.MessageBoxImage.Warning);
                return null;
            }
            finally
            {
                if (reader != null) reader.Close();
            }
        }

        private static string xmlHeaderText = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";

        /// <summary>
        /// 返回Xml文件头一行。
        /// </summary>
        public static string XmlHeaderText { get { return xmlHeaderText; } }

        /// <summary>
        /// 将文本中的小于号和&符转换成&lt;和&amp;以防止造成Xml格式失效。
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static string ReplaceXmlChars(string source)
        {
            if (source == null) return null;
            if (source.Length <= 0) return string.Empty;

            //Xml预定的转义字符有五个，只有“<”和“&”是严格禁止使用的。
            string result = source.Replace("&", "&amp;");
            result = result.Replace("<", "&lt;");
            result = result.Replace(">", "&gt;");
            result = result.Replace("'", "&apos;");
            result = result.Replace("\"", "&quot;");
            return result;
        }

        /// <summary>
        /// 将Xml文本中的&lt;和&amp;转换回小于号和&符。
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static string RestoreXmlChars(string source)
        {
            if (source == null) return null;
            if (source.Length <= 0) return string.Empty;

            //Xml预定的转义字符有五个，只有“<”和“&”是严格禁止使用的。
            string result = source.Replace("&quot;", "\"");
            result = result.Replace("&apos;", "'");
            result = result.Replace("&gt;", ">");
            result = result.Replace("&lt;", "<");
            result = result.Replace("&amp;", "&");
            return result;
        }
    }
}
